package com.aps.demo;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import lombok.NoArgsConstructor;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

/**
 * 工序类
 * 
 * 在APS排程系统中，工序是生产流程的基本执行单元。
 * 每个工序属于特定订单，需要在特定设备上执行，具有明确的执行顺序和时间要求。
 * 
 * 业务场景：
 * - 订单J1的第一道工序：在设备M1上进行车削加工，单位时间90分钟/个
 * - 订单J1的第二道工序：在设备M2上进行铣削加工，单位时间130分钟/个
 * - 每个工序都包含多个产品的加工时间区间
 * 
 * @author APS System
 * @version 1.0
 */
@NoArgsConstructor
public class Operation {
    
    /** 时间格式化器 */
    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
    
    /** 工序唯一标识符 */
    private String id;
    
    /** 所属订单 */
    private Order order;
    
    /** 绑定设备类型 */
    private String machineType;
    
    /** 单位产品加工时间（分钟/个） */
    private int unitProcessingTime;
    
    /** 总加工时间（分钟）= 单位时间 × 订单数量 */
    private int totalProcessingTime;
    
    /** 工序在订单中的执行顺序（从1开始） */
    private int sequence;
    
    /** 单个产品的工序时间区间列表 */
    private List<ProductInterval> productIntervals;
    
    // 排产结果字段
    
    /** 整个工序的开始时间（第一个产品开始加工的时间） */
    private LocalDateTime startTime;
    
    /** 整个工序的结束时间（最后一个产品完成加工的时间） */
    private LocalDateTime endTime;
    
    /** 是否涉及加班时间 */
    private boolean isOvertime;

    List<TimeInterval> overtimeIntervals = new ArrayList<>(); // 该产品在本工序的加班时间


    public void newInstanceProperty(){
        productIntervals = new ArrayList<>();
    }

    // 时间区间辅助类
    static class TimeInterval {
        LocalDateTime start;
        LocalDateTime end;

        TimeInterval(LocalDateTime start, LocalDateTime end) {
            this.start = start;
            this.end = end;
        }

        // 计算区间长度（分钟）
        long getDurationMinutes() {
            return Duration.between(start, end).toMinutes();
        }
    }

    /** 是否存在机器时间冲突 */
    private boolean hasMachineConflict;

    /**
     * 构造函数
     * 
     * @param id 工序ID
     * @param order 所属订单
     * @param machineType 执行设备类型
     * @param unitProcessingTime 单位加工时间（分钟/个）
     * @param sequence 工序顺序号
     */
    public Operation(String id, Order order, String machineType, int unitProcessingTime, int sequence) {
        if (id == null || id.trim().isEmpty()) {
            throw new IllegalArgumentException("工序ID不能为空");
        }
        if (order == null) {
            throw new IllegalArgumentException("所属订单不能为空");
        }
        if (StrUtil.isEmpty(machineType)) {
            throw new IllegalArgumentException("执行设备不能为空");
        }
        if (unitProcessingTime <= 0) {
            throw new IllegalArgumentException("单位加工时间必须大于0");
        }
        if (sequence <= 0) {
            throw new IllegalArgumentException("工序顺序号必须大于0");
        }
        
        this.id = id;
        this.order = order;
        this.machineType = machineType;
        this.unitProcessingTime = unitProcessingTime;
        this.sequence = sequence;
        this.totalProcessingTime = unitProcessingTime * order.getQuantity();
        this.productIntervals = new ArrayList<>();
        this.hasMachineConflict = false;
        this.isOvertime = false;
    }
    
    /**
     * 更新总加工时间
     * 
     * 当订单数量发生变化时，需要重新计算总加工时间
     */
    public void updateTotalProcessingTime() {
        this.totalProcessingTime = unitProcessingTime * order.getQuantity();
    }
    
    /**
     * 添加产品时间区间
     * 
     * @param productInterval 产品时间区间
     */
    public void addProductInterval(ProductInterval productInterval) {
        if (productInterval == null) {
            throw new IllegalArgumentException("产品时间区间不能为空");
        }
        this.productIntervals.add(productInterval);
        
        // 更新工序的整体开始和结束时间
        updateOperationTimeRange();
    }
    
    /**
     * 清空所有产品时间区间
     */
    public void clearProductIntervals() {
        this.productIntervals.clear();
        this.startTime = null;
        this.endTime = null;
    }
    
    /**
     * 根据产品时间区间更新工序的整体时间范围
     */
    private void updateOperationTimeRange() {
        if (productIntervals.isEmpty()) {
            this.startTime = null;
            this.endTime = null;
            return;
        }
        
        // 找到最早的开始时间和最晚的结束时间
        this.startTime = productIntervals.stream()
                .map(ProductInterval::getStart)
                .min(LocalDateTime::compareTo)
                .orElse(null);
        
        this.endTime = productIntervals.stream()
                .map(ProductInterval::getEnd)
                .max(LocalDateTime::compareTo)
                .orElse(null);
    }
    
    /**
     * 检查工序是否已完成排产
     * 
     * @return 如果已安排了所有产品的时间区间返回true，否则返回false
     */
    public boolean isScheduled() {
        return productIntervals.size() == order.getQuantity() && 
               startTime != null && endTime != null;
    }
    
    /**
     * 计算工序的实际执行时长（考虑工作时间限制）
     * 
     * @return 实际执行时长（分钟）
     */
    public long getActualDurationInMinutes() {
        if (startTime == null || endTime == null) {
            return 0;
        }
        return java.time.Duration.between(startTime, endTime).toMinutes();
    }
    
    /**
     * 检查工序是否延迟（相对于订单交付期限）
     * 
     * @return 如果工序结束时间超过订单交付期限返回true，否则返回false
     */
    public boolean isDelayed() {
        return endTime != null && endTime.isAfter(order.getDueDate());
    }
    
    /**
     * 计算工序的延迟时间（分钟）
     * 
     * @return 延迟时间（分钟），如果没有延迟返回0
     */
    public long getDelayInMinutes() {
        if (!isDelayed()) {
            return 0;
        }
        return java.time.Duration.between(order.getDueDate(), endTime).toMinutes();
    }
    
    /**
     * 检查指定时间是否在工序执行期间
     * 
     * @param time 要检查的时间
     * @return 如果时间在工序执行期间返回true，否则返回false
     */
    public boolean isExecutingAt(LocalDateTime time) {
        if (startTime == null || endTime == null) {
            return false;
        }
        return !time.isBefore(startTime) && !time.isAfter(endTime);
    }
    
    // Getter和Setter方法
    
    /**
     * 获取工序ID
     * 
     * @return 工序ID
     */
    public String getId() {
        return id;
    }
    
    /**
     * 设置工序ID
     * 
     * @param id 工序ID
     */
    public void setId(String id) {
        this.id = id;
    }
    
    /**
     * 获取所属订单
     * 
     * @return 所属订单
     */
    public Order getOrder() {
        return order;
    }
    
    /**
     * 设置所属订单
     * 
     * @param order 所属订单
     */
    public void setOrder(Order order) {
        this.order = order;
        if (order != null) {
            updateTotalProcessingTime();
        }
    }
    
    /**
     * 获取执行设备类型
     * 
     * @return 执行设备类型
     */
    public String getMachineType() {
        return machineType;
    }
    
    /**
     * 设置执行设备类型
     * 
     * @param machineType 执行设备类型
     */
    public void setMachineType(String machineType) {
        this.machineType = machineType;
    }
    
    /**
     * 获取单位加工时间
     * 
     * @return 单位加工时间（分钟/个）
     */
    public int getUnitProcessingTime() {
        return unitProcessingTime;
    }
    
    /**
     * 设置单位加工时间
     * 
     * @param unitProcessingTime 单位加工时间（分钟/个）
     */
    public void setUnitProcessingTime(int unitProcessingTime) {
        if (unitProcessingTime <= 0) {
            throw new IllegalArgumentException("单位加工时间必须大于0");
        }
        this.unitProcessingTime = unitProcessingTime;
        updateTotalProcessingTime();
    }
    
    /**
     * 获取总加工时间
     * 
     * @return 总加工时间（分钟）
     */
    public int getTotalProcessingTime() {
        return totalProcessingTime;
    }
    
    /**
     * 获取工序顺序号
     * 
     * @return 工序顺序号
     */
    public int getSequence() {
        return sequence;
    }
    
    /**
     * 设置工序顺序号
     * 
     * @param sequence 工序顺序号
     */
    public void setSequence(int sequence) {
        if (sequence <= 0) {
            throw new IllegalArgumentException("工序顺序号必须大于0");
        }
        this.sequence = sequence;
    }
    
    /**
     * 获取产品时间区间列表
     * 
     * @return 产品时间区间列表
     */
    public List<ProductInterval> getProductIntervals() {
        return productIntervals;
    }
    
    /**
     * 设置产品时间区间列表
     * 
     * @param productIntervals 产品时间区间列表
     */
    public void setProductIntervals(List<ProductInterval> productIntervals) {
        this.productIntervals = productIntervals != null ? productIntervals : new ArrayList<>();
        updateOperationTimeRange();
    }
    
    /**
     * 获取工序开始时间
     * 
     * @return 工序开始时间
     */
    public LocalDateTime getStartTime() {
        return startTime;
    }
    
    /**
     * 设置工序开始时间
     * 
     * @param startTime 工序开始时间
     */
    public void setStartTime(LocalDateTime startTime) {
        this.startTime = startTime;
    }
    
    /**
     * 获取工序结束时间
     * 
     * @return 工序结束时间
     */
    public LocalDateTime getEndTime() {
        return endTime;
    }
    
    /**
     * 设置工序结束时间
     * 
     * @param endTime 工序结束时间
     */
    public void setEndTime(LocalDateTime endTime) {
        this.endTime = endTime;
    }
    
    /**
     * 检查是否涉及加班时间
     * 
     * @return 是否涉及加班时间
     */
    public boolean isOvertime() {
        return isOvertime;
    }
    
    /**
     * 设置是否涉及加班时间
     * 
     * @param overtime 是否涉及加班时间
     */
    public void setOvertime(boolean overtime) {
        isOvertime = overtime;
    }
    
    /**
     * 检查是否存在机器冲突
     * 
     * @return 是否存在机器冲突
     */
    public boolean hasMachineConflict() {
        return hasMachineConflict;
    }
    
    /**
     * 设置是否存在机器冲突
     * 
     * @param hasMachineConflict 是否存在机器冲突
     */
    public void setHasMachineConflict(boolean hasMachineConflict) {
        this.hasMachineConflict = hasMachineConflict;
    }
    
    /**
     * toString方法，便于调试和日志输出
     * 
     * @return 格式化的字符串表示
     */
    @Override
    public String toString() {
        return String.format("工序[%s]: 订单=%s, 设备=%s, 顺序=%d, 单位时间=%d分钟, 总时间=%d分钟, 产品数=%d", 
                id, 
                order != null ? order.getId() : "null",
                machineType != null ? machineType : "null",
                sequence,
                unitProcessingTime,
                totalProcessingTime,
                productIntervals.size());
    }
    
    /**
     * equals方法，基于工序ID进行比较
     * 
     * @param obj 要比较的对象
     * @return 如果工序ID相同返回true，否则返回false
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        
        Operation operation = (Operation) obj;
        return id != null ? id.equals(operation.id) : operation.id == null;
    }
    
    /**
     * hashCode方法，基于工序ID
     * 
     * @return 散列值
     */
    @Override
    public int hashCode() {
        return id != null ? id.hashCode() : 0;
    }
}
